//marvin.c
//Chatbot for a server.

#include <stdio.h>
#include <stdlib.h> //for itoa
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include "util.h"
#include "parse.h"
#include "chatsvr.h"

static int status = 0; //keeps track of program success or failure

struct server {
    int fd;
    char buf[MAXMESSAGE+MAXHANDLE+16];
    int bytes_in_buf;
    char *nextpos;
};

int main(int argc, char **argv) {
    int port;
    char *hostname;
    struct server s;
    struct sockaddr_in r;
    fd_set fdlist;
    extern struct sockaddr_in setup_socket(char *hostname, int port);
    extern char *myreadline(struct server *s);
    extern int begins(char *msg, char *match);
    
    if (argc > 3 || argc <= 1) {
        status = 2;
        fprintf(stderr, "usage: %s HOSTNAME [PORT]\n", argv[0]);
    } else {
        //parse arguments
        if (argc == 3)
            port = atoi(argv[2]);
        else
            port = 1234;
        hostname = argv[1];

        //initialize socket
        if ((s.fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket");
            status = 1;
        }

        //setup socket properties
        r = setup_socket(hostname, port);
        if (status != 1) { 
            //connect to socket
            if (connect(s.fd, (struct sockaddr *)&r, sizeof r) < 0) {
                perror("connect");
                status = 1;
            } else {
                //connect with default bot name
                write(s.fd, "Marvin\r\n", 8);
                

                //setup rest of server properties required for reading
                //a string
                s.bytes_in_buf = 0;
                s.nextpos = NULL;

                //get banner
                while (strcmp(myreadline(&s), CHATSVR_ID_STRING) != 0 &&
                    s.bytes_in_buf <= sizeof CHATSVR_ID_STRING);

                //did we not receive the server id string? in that case
                //we're being run on a different server program, exit
                if (s.bytes_in_buf > sizeof CHATSVR_ID_STRING)
                    perror("chat server");

                //main chatbot loop; either reading message from server 
                //or stdin
                int done = 0;
                int maxfd = s.fd;
                while (1) {
                    FD_ZERO(&fdlist);
                    FD_SET(s.fd, &fdlist);
                    FD_SET(0, &fdlist);
                    switch (select(maxfd+1, &fdlist, NULL, NULL, NULL)) {
                        case 0:
                            //timeout; this should never happen
                            done = 1;
                            break;
                        case -1:
                            //something went wrong with select
                            perror("select");
                            done = 1;
                            break;
                        default:
                            if (FD_ISSET(s.fd, &fdlist)) {
                                //read from server
                                char *msg = myreadline(&s);
                                if (msg != NULL) {
                                    //read handle
                                    char handle[MAXHANDLE];
                                    int i = 0;
                                    while (msg[i] != ':' && i < MAXHANDLE) {
                                        handle[i] = msg[i];
                                        i++;
                                    }
                                    handle[i] = '\0';
                                    //shift over to where the message actually
                                    //starts
                                    msg += i+2;

                                    if (begins(msg, "Hey Marvin,")) {
                                        //grab expression
                                        char expr[strlen(msg)-10];
                                        int j;
                                        for (j = 12; j < strlen(msg); j++) {
                                            expr[j-12] = msg[j+strlen(handle)+6];
                                        }
                                        expr[j+1] = '\0';
                                        struct expr *pexpr = parse(expr);
                                        if (pexpr == NULL) {
                                            write(s.fd, "Hey ", 4);
                                            write(s.fd, handle, strlen(handle));
                                            write(s.fd, ", I don't like that.\r\n",
                                                21);
                                        } else {
                                            char result[33];
                                            sprintf(result, "%d", 
                                                evalexpr(pexpr));
                                            write(s.fd, "Hey ", 4);
                                            write(s.fd, handle, strlen(handle));
                                            write(s.fd, result, 
                                                strlen(result));
                                            write(s.fd, "\r\n", 2);
                                        }
                                    }
                                }
                            } else if (FD_ISSET(0, &fdlist)) {
                                //read from stdin, write to server
                                char msg[MAXMESSAGE];
                                fgets(msg, MAXMESSAGE-2, stdin);
                                write(s.fd, msg, strlen(msg));
                            } else {
                                //this should never happen; pass
                            }
                    }
                    if (done)
                        break;
                }
                
            }
        }
    }
    return(status);
}

//sets up the socket properties from hostname and port
struct sockaddr_in setup_socket(char *hostname, int port) {
    struct hostent *hp;
    struct sockaddr_in r;
    //get host by name, or call foul if we can't get anything
    if ((hp = gethostbyname(hostname)) == NULL) {
        fprintf(stderr, "%s: no such host\n", hostname);
        status = 1;
    }
    
    //make sure it's a valid host
    if (hp->h_addr_list[0] == NULL || hp->h_addrtype != AF_INET) {
        fprintf(stderr, "%s: not an internet protocol host name\n", hostname);
        status = 1;
    }

    //setup socket properties
    memset(&r, '\0', sizeof r);
    r.sin_family = hp->h_addrtype;
    memcpy(&r.sin_addr, hp->h_addr_list[0], hp->h_length);
    r.sin_port = htons(port);

    //success!
    return(r);
}

//reads a line from the server
char *myreadline(struct server *s) {
    int nbytes;

    //move leftover data to beginning of buffer
    if (s->bytes_in_buf && s->nextpos)
        memmove(s->buf, s->nextpos, s->bytes_in_buf);

    //if we have a whole line already, no need to read, just return
    if ((s->nextpos = extractline(s->buf, s->bytes_in_buf))) {
        s->bytes_in_buf -= (s->nextpos - s->buf);
        return(s->buf);
    }

    //so we don't have a whole line. Let's try to read until we get a whole
    //line, or maybe we won't, in which case let's almost fill the buffer
    //(leaving room for \0).
    nbytes = read(s->fd, s->buf + s->bytes_in_buf, 
        sizeof s->buf - s->bytes_in_buf - 1);
    if (nbytes < 0) {
        perror("read()");
    } else if (nbytes == 0) {
        //the server isn't sending anything, so don't react
    } else {
        s->bytes_in_buf += nbytes;

        //is our whole line here yet?
        if ((s->nextpos = extractline(s->buf, s->bytes_in_buf))) {
            s->bytes_in_buf -= (s->nextpos - s->buf);
            return(s->buf);
        }
        
        //okay, still not here. If we've hit the maximum message size though,
        //we should send what we have as a line since we can't read in
        //anything more
        if (s->bytes_in_buf >= MAXMESSAGE+MAXHANDLE) {
            s->buf[s->bytes_in_buf] = '\0';
            s->bytes_in_buf = 0;
            s->nextpos = NULL;
            return(s->buf);
        }
   }

   //bad news, everyone! we don't have a full line yet!
   return(NULL);
}

//checks if msg begins with the string match
int begins(char *msg, char *match) {
    int num_bytes = strlen(match);
    if (num_bytes <= strlen(msg)) {
        char beginning[num_bytes+1];
        strncpy(beginning, msg, num_bytes+1);
        if (strcmp(beginning, match) == 0) {
            //match found!
            return(1);
        }
    }
    return(0);
}
